class Compiler {
  constructor(vm) {
    // 保存数据
    this.vm = vm
    this.el = vm.$el
    // 调用方法处理容器内容
    this.compile(this.el)
  }
  // vue 挂载容器内容
  compile (el) {
    // 遍历所有挂载 容器下面的节点
    Array.from(el.childNodes).forEach(node => {
      if (node.nodeType === 3) {
        // 如果是文本节点，调用文本节点编译方法
        this.compileText(node)
      } else if (node.nodeType === 1) {
        // 如果是元素节点，调用元素节点编译方法
        this.compileElement(node)
      }
      // 判断该节点是否存在子节点，存在利用递归再次遍历
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }
  // 文本节点编译方法
  compileText (node) {
    // 创建一个正则，用来筛选模版字符串
    const reg = /\{\{(.+?)\}\}/g
    // 过滤文本内容，去掉全部空白字符
    const newtext = node.textContent.replace(/\s/g, '')
    // 创建一个数组接纳所有需要拼接的字符串
    const strs = []
    // 匹配到的模版字符串在整个字符串中结束位置：下面循环中需要
    let lastIndex = 0
    // 匹配到的模版字符串在整个字符串中开始位置：下面循环中需要
    let nowIndex = 0
    // 创建一个变量接纳每次匹配出来的结果
    let result
    // 遍历字符串匹配结果
    while (result = reg.exec(newtext)) {
      // 将匹配到的开始位置设置给
      nowIndex = result.index
      // 如果当前位置大于 lastIndex 表示中间有字符串尚未保存，那么把两个位置中间的字符串保存进数组
      if (nowIndex > lastIndex) strs.push(newtext.slice(lastIndex, nowIndex))
      // 修改lastIndex以便下次再截取（加上模版字符串本身长度，因为这段长度会被替换掉）
      lastIndex = nowIndex + result[0].length
      // 将再vm中匹配到的属性值添加进数组
      strs.push(this.vm[result[1]])
      // 记住当前模版字符串再数组中的位置
      const meIndex = strs.length - 1
      // 创建订阅者（每个模版字符串都是一个订阅者）
      new Watcher(this.vm, result[1], newValue => {
        // 把修改后的新值设置到数组中
        strs[meIndex] = newValue
        // 数组转字符串设置给文本节点内容
        node.textContent = strs.join('')
      })
    }
    // 最后判断一下如果最后一个模版字符串后面还有东西，则将后面的文本也添加到数组中
    if (lastIndex < newtext.length) strs.push(newtext.slice(lastIndex))
    // 初次渲染是也要做数组转字符串设置内容
    node.textContent = strs.join('')
  }
  // 元素节点编译方法
  compileElement (node) {
    // 遍历节点全部属性，一旦碰到以 v- 开头的属性名。立马调用 updata 对属性进行处理
    Array.from(node.attributes).forEach(attr => {
      if (attr.name.slice(0,2) !== 'v-') return
      this.updata(node, attr.name.slice(2), attr.value)
    })
  }
  // v- 开头的属性处理
  updata (node, name, key) {
    // 调用 this 里相应的方法
    this[name](node, key, this.vm[key])
  }
  // v-text 处理方法
  text (node, key, value) {
    // 创建订阅
    new Watcher(this.vm, key, newValue => {
      // 直接把新值设置给node节点
      node.textContent = newValue
    })
    // 初始化设置
    node.textContent = value
  }
  // v-model 处理方法
  model (node, key, value) {
    // 同样创建订阅
    new Watcher(this.vm, key, newValue => {
      // data变化直接修改
      node.value = newValue
    })
    // 初始化设置
    node.value = value
    // 由于v-mode是双向绑定，在这里给node节点绑定input方法
    node.addEventListener('input', () => {
      // input 的时候修改 vm 里的对应属性值
      this.vm[key] = node.value
    })
  }
}